package com.irssproject.app.service.clustering.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.didion.jwnl.JWNL;
import net.didion.jwnl.JWNLException;
import net.didion.jwnl.data.IndexWord;
import net.didion.jwnl.data.POS;
import net.didion.jwnl.data.PointerType;
import net.didion.jwnl.data.Synset;
import net.didion.jwnl.data.list.PointerTargetNode;
import net.didion.jwnl.data.list.PointerTargetNodeList;
import net.didion.jwnl.data.relationship.Relationship;
import net.didion.jwnl.data.relationship.RelationshipFinder;
import net.didion.jwnl.data.relationship.RelationshipList;
import net.didion.jwnl.dictionary.Dictionary;
import net.htmlparser.jericho.Source;

import org.carrot2.clustering.lingo.LingoClusteringAlgorithm;
import org.carrot2.core.Cluster;
import org.carrot2.core.Controller;
import org.carrot2.core.ControllerFactory;
import org.carrot2.core.Document;
import org.carrot2.core.ProcessingResult;
import org.carrot2.core.attribute.AttributeNames;
import org.carrot2.text.vsm.LinearTfIdfTermWeighting;
import org.carrot2.text.vsm.TermDocumentMatrixBuilder;
import org.carrot2.util.attribute.AttributeUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.Resource;

import com.google.api.translate.Language;
import com.google.api.translate.Translate;
import com.irssproject.app.bean.clustering.ClusterNode;
import com.irssproject.app.bean.clustering.ClusterTree;
import com.irssproject.app.bean.lectorRSS.Etiqueta;
import com.irssproject.app.bean.lectorRSS.Lectura;
import com.irssproject.app.bean.lectorRSS.Subscripcio;
import com.irssproject.app.bean.main.Usuari;
import com.irssproject.app.dao.lectorRSS.iface.SubscripcioDAO;
import com.irssproject.app.service.clustering.iface.ClusteringEngineService;
import com.irssproject.app.utils.Constants;
import com.sun.accessibility.internal.resources.accessibility;

import freemarker.log.Logger;

@SuppressWarnings("unchecked")
public class ClusteringEngineServiceImpl implements ClusteringEngineService {
	
	private final int MAX_CLUSTERS_SIZE = 20;

	Logger logger = Logger.getLogger(ClusteringEngineServiceImpl.class.getName());

	SubscripcioDAO subscripcioDAO;

	public void setSubscripcioDAO(SubscripcioDAO subscripcioDAO) {
		this.subscripcioDAO = subscripcioDAO;
	}

	public List<String> clusteringByFeed(Usuari usuari)
	{
		
		List<Subscripcio> subscripcions = subscripcioDAO.getListSubscripcions(usuari);

		Iterator<Subscripcio> subscr = subscripcions.iterator();
		String[] labarray = null;
		
		List<String> globalRetorn = new ArrayList<String>();

		while (subscr.hasNext()) {

			Subscripcio subscripcio = subscr.next();
			
			List<Lectura> lectures = subscripcioDAO.getLecturesBySubscripcioId(subscripcio.getId());

			ArrayList<Document> documents = formatItemsToDocuments(lectures);

			ProcessingResult result = clusteringWithLingo(documents, 100);

			Iterator<Cluster> iter = result.getClusters().iterator();

			String lab = "";

			Cluster cl;

			System.out.println("Per la subscripcio "+subscripcio.getFeed().getTitol());
			
			int i = 0;
			int len;
			if (result.getClusters().size()>MAX_CLUSTERS_SIZE)
			{
				len = MAX_CLUSTERS_SIZE;
			}
      		else
      		{
      			len = result.getClusters().size();
      		}
      		    
      		
			labarray = new String[len];		
//			System.out.println("**************CLUSTERS GENERATED*******************");
 			while (iter.hasNext() && i<labarray.length) {
 				cl = iter.next();
 				lab = cl.getLabel();
 				labarray[i] = traductorGoogle(lab);		
//   				System.out.println("----> label(translated): "+labarray[kk]+" ----> num of docs: "+cl.getDocuments().size());
 				i++;
 			}

 			List<String> result1 = clusteringAlgorithm(labarray);
 			globalRetorn.addAll(result1);
		}
      	
		return globalRetorn;

	}

	public List<HashMap<String, String>> clusteringByLabel(Usuari usuari) 
	{

		ArrayList<Etiqueta> listEtiquetes = (ArrayList<Etiqueta>) subscripcioDAO
				.getListEtiquetes(usuari);
		Iterator<Etiqueta> itEtiq = listEtiquetes.iterator();

		List<HashMap<String, String>> globalRetorn = new ArrayList<HashMap<String, String>>();
		
		while (itEtiq.hasNext()) {
			Etiqueta etiq = itEtiq.next();
			System.out.println("******************************");
			System.out.println("Etiqueta: " + etiq.getNom());
			System.out.println("******************************");

			ArrayList<Subscripcio> subscripcionsEtiq = (ArrayList<Subscripcio>) subscripcioDAO
					.getListSubscripcionsPerEtiqueta(usuari, etiq);
			Iterator<Subscripcio> subscr = subscripcionsEtiq.iterator();

			int i = 0;
			ArrayList a = new ArrayList();

			HashMap<String, String> hm = new HashMap<String, String> ();
			while (subscr.hasNext()) {

				Subscripcio subscripcio = subscr.next();
				List<Lectura> lectures = subscripcioDAO.getLecturesBySubscripcioId(subscripcio.getId());

				ArrayList<Document> documents = formatItemsToDocuments(lectures);

				ProcessingResult result = clusteringWithLingo(documents, 30);

				Iterator<Cluster> iter = result.getClusters().iterator();
				String lab = "";

				int midaClusters=result.getClusters().size();
				if(midaClusters>MAX_CLUSTERS_SIZE)
				{
					midaClusters=MAX_CLUSTERS_SIZE;
				}
				
				Cluster cl;
				int j = 0;
				int max = 0;
			    
				while (iter.hasNext() && max<MAX_CLUSTERS_SIZE) {
					cl = iter.next();
					lab = cl.getLabel();
					a.add(traductorGoogle(lab));
					max++;
					j++;
				}
				i++;
			}

			Iterator it = a.iterator();
			String[] labarray = new String[a.size()];

			int k=0;
			while(it.hasNext())
			{
				labarray[k++] = (String)it.next();
			}

			System.out.println("***********************************");
			System.out.println("Paraules que entren a l'algorisme: ");

			for (int kk=0;kk<labarray.length;kk++)
			{
				System.out.print(labarray[kk]+", ");
			}
			System.out.println("***********************************");
			
 			List<String> result = clusteringAlgorithm(labarray);

 			hm = new HashMap<String, String>();
 			hm.put("etiqueta", etiq.getNom());
 			hm.put("llistaClusters", result.toString());
 			
 			globalRetorn.add(hm);
		}
		return globalRetorn;

	}

	public void clusteringByUser(Usuari usuari) 
	{

//		List<Subscripcio> subscripcions = subscripcioDAO
//				.getListSubscripcions(usuari);
//
//		Iterator subscr = subscripcions.iterator();
//
//		String[][] clustersPerSubscr = new String[subscripcions.size()][15];
//
//		int i = 0;
//
//		while (subscr.hasNext()) {
//			Subscripcio subscripcio = (Subscripcio) subscr.next();
//
//			// TODO arreglar aixo canviar null per un valor logic
//			subscripcio = subscripcioDAO.getSubscripcio(usuari,
//					null);
//			List items = subscripcioDAO.getTotsItems(subscripcio);
//
//			ArrayList<Document> documents = formatItemsToDocuments(items);
//
//			ProcessingResult result = clusteringWithLingo(documents, 100);
//
//			Iterator<Cluster> iter = result.getClusters().iterator();
//			String lab = "";
//
//			String[] labelsList = new String[result.getClusters().size()];
//
//			Cluster cl;
//			int j = 0;
//		    System.out.println("Per la subscripcio "+subscripcio.getFeed().getTitol());
//
//			while (iter.hasNext()) {
//				cl = iter.next();
//				lab = cl.getLabel();
//				labelsList[j] = lab;
//			    System.out.println("----> label: "+lab+" ----> num of docs: "+cl.getDocuments().size());
//				j++;
//			}
//
//		    System.out.println("");
//
//			clustersPerSubscr[i] = new String[labelsList.length];
//			clustersPerSubscr[i] = labelsList;
//
//			i++;
//
//		}
//
//		generatePreparedClustersList(clustersPerSubscr);

	}
		
	private List<String> clusteringAlgorithm(String[] clusterArray){
		
		List<String> retorn = new ArrayList<String>();

		try {
	        
			ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");					
	  		Resource resource = ctx.getResource("classpath:file_properties.xml");
			JWNL.initialize(resource.getInputStream());
			Dictionary dict = Dictionary.getInstance();
			List<ClusterTree> llistaClusterTrees = new ArrayList<ClusterTree>();
			ClusterTree clTree, clTree1;
			Iterator<PointerTargetNode> itNodesRelacio;
			Iterator<Relationship> iterLlistaRelacions;
			/*
			 * Iterem sobre la llista de clusters,
			 * comparem cada element amb tots els dem�s de la llista
			 * amb la finalitat de trobar-hi relacions
			 */
			for (int i1 = 0; i1<clusterArray.length;i1++)
			{
				IndexWord paraula1 = dict.getIndexWord(POS.NOUN, clusterArray[i1]);

				for (int j1=i1+1;j1<clusterArray.length;j1++)
				{
					IndexWord paraula2 = dict.getIndexWord(POS.NOUN, clusterArray[j1]);
					
					Synset[] significatsArray1=null;
					Synset[] significatsArray2=null;
				
					try{
						
						/*
						 * Obtenim els significats de les dues paraules actuals a comparar
						 * Si no trobem un dels significats, saltem al nullpointerexception
						 * que provocara que es guardi la paraula ja que es tractara d'un nom
						 * propi (com ara Google, Microsoft o Formula1)i per tant sense 
						 * significat com a NOUN dins del diccionari de wordnet
						 */
						significatsArray1 = paraula1.getSenses();
						significatsArray2 = paraula2.getSenses();

						int profunditatRelacio = Integer.MAX_VALUE;
						long offsetRelacio = 0;

						for (int k = 0; k < significatsArray1.length; k++) 
						{
							Synset significatParaula1 = significatsArray1[k];
							for (int l = 0; l < significatsArray2.length; l++) 
							{
								Synset significatParaula2 = significatsArray2[l];
								
								//Obtenim la llista de relacions entre els dos significats
								RelationshipList llistaRelacions = RelationshipFinder.getInstance().findRelationships(significatParaula1, significatParaula2, PointerType.HYPERNYM);

								if (!llistaRelacions.isEmpty())  
								{
									iterLlistaRelacions = llistaRelacions.iterator();
																		
									while (iterLlistaRelacions.hasNext())
									{
										Relationship relacio = iterLlistaRelacions.next();
										
										PointerTargetNodeList nodelist = relacio.getNodeList();
										
										itNodesRelacio = nodelist.iterator();										
										if (itNodesRelacio.hasNext())
										{
											boolean hemTrobatElCim=false;

											ClusterNode cim = new ClusterNode();
											List<ClusterNode> llistaArbreEsquerra = new ArrayList<ClusterNode>();
											List<ClusterNode> llistaArbreDret = new ArrayList<ClusterNode>();
											clTree1 = new ClusterTree();
											PointerTargetNode paraulaActual = null;
											long offsetActual = 0;
											while (itNodesRelacio.hasNext() /*&& paraulaActual.getType().getLabel().equals("hypernym")*/)
											{
												paraulaActual = itNodesRelacio.next();
												String lemmaParaulaActual = paraulaActual.getSynset().getWord(0).getLemma();

												if(paraulaActual.getType().getLabel().equals("hyponym")) 
												{
													if(hemTrobatElCim==false)
													{
														hemTrobatElCim=true;
														cim.setValor(lemmaParaulaActual);
														offsetActual = paraulaActual.getSynset().getOffset();														
													}
													else if(hemTrobatElCim==true)
													{
														ClusterNode actual = new ClusterNode(lemmaParaulaActual);
														llistaArbreDret.add(actual);
													}
												}
												else if(paraulaActual.getType().getLabel().equals("hypernym"))
												{
													ClusterNode actual = new ClusterNode(lemmaParaulaActual);
													llistaArbreEsquerra.add(actual);	
												}										
											}	
											
											Iterator<ClusterNode> it1 = llistaArbreEsquerra.iterator();
											Iterator<ClusterNode> it2 = llistaArbreDret.iterator();

											ClusterNode[] arrayArbreEsquerra = new ClusterNode[llistaArbreEsquerra.size()];
											ClusterNode[] arrayArbreDret = new ClusterNode[llistaArbreDret.size()];
											int x1=0,x2=0;
											while (it1.hasNext())
											{
												arrayArbreEsquerra[x1] = it1.next();
												x1++;
											}
											while (it2.hasNext())
											{
												arrayArbreDret[x2] = it2.next();
												x2++;
											}
											
											for (int x = 1; x<arrayArbreEsquerra.length;x++)
											{
												arrayArbreEsquerra[x].addChild(arrayArbreEsquerra[x-1]);
											}
											for (int x = arrayArbreDret.length-1; x>0; x--)
											{
												arrayArbreDret[x-1].addChild(arrayArbreDret[x]);
											}										
											if (arrayArbreEsquerra.length>0)
												cim.addChild(arrayArbreEsquerra[arrayArbreEsquerra.length-1]);
											if (arrayArbreDret.length>0)
												cim.addChild(arrayArbreDret[0]);
											
											clTree1.setRootElement(cim);


											/*
											 * Cerquem la paraula que relacioni les dues paraules comparades 
											 * i que estigui a distancia minima i amb el menor offset
											 */
											
											if (relacio.getDepth()<profunditatRelacio && offsetActual>offsetRelacio && discardWordNetTops(cim.getValor()))
											{
												llistaClusterTrees.add(clTree1);												
												profunditatRelacio = relacio.getDepth();
												offsetRelacio = paraulaActual.getSynset().getOffset();
											}
										}
									}
								}								
							}
						}
					}
					catch(NullPointerException e)
					{

						/*Si es null vol dir que la paraula no es troba al diccionari, 
						 * i q per tant, confiant en que el redactor del post sapiga escriure, 
						 * es una paraula tecnica o molt concreta,
						 * l'afegim a la llista de paraules escollides
						 */
						
						if (significatsArray1==null)
						{
							if (!retorn.contains(clusterArray[i1]))
								retorn.add(clusterArray[i1]);
						}
						if (significatsArray2==null)
						{
							if (!retorn.contains(clusterArray[j1]))
								retorn.add(clusterArray[j1]);
						}

					}
					catch(Exception e)
					{
						e.printStackTrace();
					}
					
					

				}
			}


			int midaLlistaAbans = llistaClusterTrees.size();
			int midaLlistaDespres = Integer.MAX_VALUE;
			while (midaLlistaAbans!=midaLlistaDespres)
			{
				midaLlistaAbans = llistaClusterTrees.size();

				for (int i=0;i<llistaClusterTrees.size();i++)
				{
					clTree = llistaClusterTrees.get(i);
					for (int j=0;j<llistaClusterTrees.size();j++)
					{
						if (i!=j)
						{
							ClusterTree.setSegonArbre(llistaClusterTrees.get(j));
							ClusterTree.setSegonArbreCompletamentAbsorbit(false);
							clTree.fusionar(llistaClusterTrees.get(j));
							if (ClusterTree.isSegonArbreCompletamentAbsorbit())
							{
								llistaClusterTrees.remove(j);
								j=j-1;
							}

						}

					}

				}
				System.out.println("En aquesta passada ens quedem amb els seguents "+llistaClusterTrees.size()+" elements");
				for (int c=0;c<llistaClusterTrees.size();c++)
				{
					System.out.println(llistaClusterTrees.get(c).toString());
				}

				
				midaLlistaDespres = llistaClusterTrees.size();
			}

			
//			int lenAnteriorLlistaClusterTrees = Integer.MAX_VALUE;
//			boolean encaraEsPot = true;
//			while (llistaClusterTrees.size()!=lenAnteriorLlistaClusterTrees && encaraEsPot == true)
//			{
//				lenAnteriorLlistaClusterTrees = llistaClusterTrees.size();
//				System.out.println("La longitut "+lenAnteriorLlistaClusterTrees);
//				int cont = 0;
//				int contAux = 0;
//				encaraEsPot = false;
//				List<Integer> llistaAux = new ArrayList<Integer>();
//				for (int i=0; i<llistaClusterTrees.size();i++)
//				{
//					clTree = llistaClusterTrees.get(i);
//					String primerArbre = clTree.toString();
//					int len = primerArbre.length();
//					contAux = 0;
//					for (cont=contAux+1; cont<llistaClusterTrees.size();cont++)
//					{
////						System.out.println("************************");
////
////						System.out.println("fusionem: ");
////						System.out.println(clTree.toString());
////						System.out.println(llistaClusterTrees.get(cont).toString());
////						System.out.println("************************");
//
//						if (clTree.equals(llistaClusterTrees.get(cont)))
//						{
//							System.out.println("Ep!! aquests dos que anem a comparar son iguals");
//							System.out.println(clTree.toString());
//							System.out.println(llistaClusterTrees.get(cont).toString());
//							llistaAux.add(cont);
//						}
//						else
//						{
//							clTree.fusionar(llistaClusterTrees.get(cont));
//						}
//					}
//					if (clTree.toString().length()!=len)
//					{
//						System.out.println("afegim cltree perque es diferent al inicial");
//						System.out.println(clTree.toString());
//						System.out.println(primerArbre);
//						llistaClusterTrees.set(contAux, clTree);
//						encaraEsPot=true;
//					}
//					else
//					{
//						System.out.println("NO afegim cltree perque es identic al inicial");
//						System.out.println(clTree.toString());
//						System.out.println(primerArbre);
//						llistaAux.add(contAux);
//					}
//
//					contAux++;
//				}
//				
//				if (encaraEsPot==true)
//				{
//					Iterator<Integer> it = llistaAux.iterator();
//					while (it.hasNext())
//					{
//						llistaClusterTrees.remove(it.next());
//					}
//				}
//			}

			System.out.println("Retornarem aixo");
			for (int c=0;c<llistaClusterTrees.size();c++)
			{
				System.out.println(llistaClusterTrees.get(c).toString());
				retorn.add(llistaClusterTrees.get(c).toString());
			}

		}
		
		catch(IOException e)
		{
			e.printStackTrace();
		}
		catch(NullPointerException e)
		{
			e.printStackTrace();
		}
		catch (JWNLException e) 
		{
			e.printStackTrace();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		return retorn;

	}

	private ArrayList<Document> formatItemsToDocuments(List<Lectura> lectures) {

		final String[][] data = new String[lectures.size()][3];

		Iterator<Lectura> it = lectures.iterator();

		int i = 0;
		Source source;
		String content;
		
		while (it.hasNext() && i < 100) {
			Lectura lectura= it.next();

			data[i][0] = lectura.getItem().getTitol();
			data[i][1] = lectura.getItem().getLink();

			source = new Source(lectura.getItem().getContingut());
			content = source.getTextExtractor().setIncludeAttributes(true)
					.toString();

			data[i][2] = "" + content;

			i++;
		}

		ArrayList<Document> documents = new ArrayList<Document>();
		for (String[] row : data) {
			documents.add(new Document(row[1], row[2], row[0]));
		}

		return documents;

	}

	private String traductorGoogle(String cluster) {

		try {
			Translate.setHttpReferrer(cluster);

			String translate = "";
			translate = Translate.execute(cluster, Language.AUTO_DETECT,
					Language.ENGLISH);
			if (!translate.equalsIgnoreCase(cluster)) {
				cluster = translate;
			}
		} catch (Exception e) {
			if (e.getMessage() != null) {
				if (e.getMessage().contains(
						Constants.GOOGLE_ERROR_DETECTION_LANGUAGE)) {
					// Do nothing, let the original word
					logger
							.warn("The word "
									+ cluster
									+ " cannot be translated. Will keep the original word");
				}
			}

		}
		return cluster;

	}

	private ProcessingResult clusteringWithLingo(ArrayList<Document> documents,
			int clusterCountBase) {
		/*
		 * We are clustering using a simple controller (no caching, one-time
		 * shot).
		 */

		final Controller controller = ControllerFactory.createSimple();

		/*
		 * All data for components (and between them) is passed using a Map.
		 * Place the required attributes and tuning options in the map below
		 * before you start processing. Each document source and algorithm comes
		 * with a set of attributes that can be tweaked at runtime (during
		 * component initialization or processing of every query). Refer to each
		 * component's documentation for details.
		 */
		final Map<String, Object> attributes = new HashMap<String, Object>();
		attributes.put(AttributeNames.DOCUMENTS, documents);

		/*
		 * Now we will cluster the same documents using a more complex text
		 * clustering algorithm: Lingo. Note that the process is essentially the
		 * same, but we will set an algorithm parameter for term weighting to a
		 * non-default value to show how it is done.
		 */

		ProcessingResult result;

		attributes.clear();
		attributes.put(AttributeNames.DOCUMENTS, documents);
		attributes.put(AttributeUtils.getKey(TermDocumentMatrixBuilder.class,
				"termWeighting"), LinearTfIdfTermWeighting.class);
		attributes.put("LingoClusteringAlgorithm.phraseLengthPenaltyStop", 2);
		attributes.put("LingoClusteringAlgorithm.phraseLengthPenaltyStart", 2);
		attributes.put("LingoClusteringAlgorithm.desiredClusterCountBase",
				clusterCountBase);
		attributes.put("TermDocumentMatrixBuilder.titleWordsBoost", 2.0);
	/*	
		int midaLlindar = (int)(documents.size()*0.50);
		if (midaLlindar>100)midaLlindar=100;
		attributes.put("DocumentAssigner.minClusterSize", midaLlindar);
*/
		// attributes.put(AttributeNames.QUERY, "technology");
		result = controller.process(attributes, LingoClusteringAlgorithm.class);

		return result;

	}
	

	private boolean discardWordNetTops(String word) {

//		String word = word.getSynset().getWord(0).getLemma();
		
		if (word.equals("object") || word.equals("abstraction") ||
			word.equals("psychological_feature") ||	word.equals("event") ||
			word.equals("entity") || word.equals("physical_entity") ||
			word.equals("whole") ||	word.equals("artifact") ||
			word.equals("person") || word.equals("location") ||
			word.equals("time_period") || word.equals("instrumentality") ||
			word.equals("action") || word.equals("structure") ||
			word.equals("relation") || word.equals("property") ||
			word.equals("organism") || word.equals("attribute") ||
			word.equals("activity") || word.equals("cognition") ||
			word.equals("state") || word.equals("act") || 
			word.equals("process") || word.equals("communication") || 
			word.equals("matter") || word.equals("message") || 
			word.equals("causal_agent") || word.equals("material") ||
			word.equals("concept") || word.equals("measure") ||
			word.equals("work") || word.equals("fundamental_quantity") ||
			word.equals("measure") || word.equals("basic_cognitive_process") ||
			word.equals("quality") || word.equals("content") ||
			word.equals("part") || word.equals("natural_object") ||
			word.equals("thing") || word.equals("collection") ||
			word.equals("change_of_state") || word.equals("unit") ||
			word.equals("figure") || word.equals("equipment") ||
			word.equals("possession") || word.equals("number") ||
			word.equals("happening") || word.equals("organization") ||
			word.equals("higher_cognitive_process") || word.equals("there") ||
			word.equals("country") || word.equals("day_of_the_week") ||
			word.equals("statement") || word.equals("occupation") ||
			word.equals("status") || word.equals("deed") ||
			word.equals("category") || word.equals("change") ||
			word.equals("agent") || word.equals("goal") ||
			word.equals("point") || word.equals("device") ||
			word.equals("writing") || word.equals("written_communication") ||
			word.equals("substance") || word.equals("physical_condition") ||
			word.equals("reproductive_structure") || word.equals("physical_condition") ||
			word.equals("transferred_property") || word.equals("Gregorian_calendar_month") ||
			word.equals("physical_phenomenon") || word.equals("natural_phenomenon") ||
			word.equals("region") || word.equals("land") ||
			word.equals("group"))
		{

		return false;
		}
		else return true;
	}

	static class ClusterFreq {
		public ClusterFreq(String cluster, int cont) {
			this.label = cluster;
			this.value = cont;
		}

		String label;
		int value;
	}


}
